package com.marketlive.system.config.spring;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/*
(C) Copyright MarketLive. 2006. All rights reserved.
MarketLive is a trademark of MarketLive, Inc.
Warning: This computer program is protected by copyright law and international treaties.
Unauthorized reproduction or distribution of this program, or any portion of it, may result
in severe civil and criminal penalties, and will be prosecuted to the maximum extent
possible under the law.
*/

import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.marketlive.system.config.IConfigurationContext;
import org.marketlive.system.config.IConfigurationBranch;
import org.marketlive.system.config.IConfigurationLeaf;

import java.util.Properties;
import java.io.File;

import com.marketlive.system.config.filesystem.FileSystemLoader;
import com.marketlive.system.config.ConfigurationManager;
import com.marketlive.system.config.ConfigurationBranch;
import com.marketlive.system.config.ConfigurationToString;
import com.marketlive.system.config.TokenResolver;
import com.marketlive.system.config.properties.PropertiesLoader;

/**
 * Injects variables into the context files.
 */
public class SpringPlaceholderConfigurer extends PropertyPlaceholderConfigurer {

  /** Logger. */
  private static Log log = LogFactory.getLog(SpringPlaceholderConfigurer.class);

  /**
   * The configuration manager instantiated and used by this configurator.
   */
  private ConfigurationManager configurationManager;

  /**
   * Semicolon-separated list of files and/or directories to ignore. Files or directories with names matching the ignore
   * list are not loaded.
   */
  private String ignoreList = null;
  /** Configuration root. */
  private String configurationRoot = null;

  /**
   * Uses the environment variable MARKETLIVE_SITE_HOME to determine the configuration root.
   *
   */
  public SpringPlaceholderConfigurer() {
    this(null, null);
  }

  /**
   * Sets the properties from the configuration manager.
   *
   * @param configurationRoot the path to the root directory for the configuration.
   */
  public SpringPlaceholderConfigurer(String configurationRoot) {
    this(configurationRoot, null);
  }

  /**
   * Sets the properties from the configuration manager.
   *
   * @param configurationRoot the path to the root directory for the configuration. If null, the environment variable
   *        MARKETLIVE_SITE_HOME is used to determine the configuration path.
   * @param ignoreList a list of files and directories to ignore (not recurse). If null, the environment variable
   *        MARKETLIVE_CONFIG_IGNORE_LIST is used.
   */
  public SpringPlaceholderConfigurer(String configurationRoot, String ignoreList) {

    if (configurationRoot == null || configurationRoot.length() == 0) {
      setConfigurationRoot(System.getProperty(ConfigurationManager.CONTEXT_KEY_PREFIX + ConfigurationManager.SITE_HOME)
          + File.separator + "conf");
    } else {
      setConfigurationRoot(configurationRoot);
    }
    if (ignoreList == null) {
      setIgnoreList(System.getProperty(ConfigurationManager.CONTEXT_KEY_PREFIX
          + ConfigurationManager.CONFIG_IGNORE_LIST));
    } else {
      setIgnoreList(ignoreList);
    }
    init();
  }

  /**
   * Initializes the configurator.
   *
   */
  public void init() {

    // make sure the configurationRoot points to a config directory with the required
    // default subdirectory.
    File baseDir;
    baseDir = new File(configurationRoot + "/default");
    if (!baseDir.exists()) {
      throw new RuntimeException("Does not appear to be a valid configuration directory: " + configurationRoot);
    }
    // System.out.println("SpringRlaceholderConfigurer::init( ): baseDir='" + baseDir + "'");

    // load the base configuration
    configurationManager = new ConfigurationManager();
    FileSystemLoader loader = new FileSystemLoader();
    loader.setIgnoreList(ignoreList);
    loader.loadFromFileSystem(configurationManager, baseDir);

    // overload with the other dimensions
    IConfigurationContext context = configurationManager.getContext();
    // System.out.println("context = " + context.toString());

    // these are in the appropriate order so that overrides happen as specified
    loadConfigurationDimension(configurationRoot, ConfigurationManager.CONTEXT_SITE_KEY, "site", context.getClient(),
        loader);
    loadConfigurationDimension(configurationRoot, ConfigurationManager.CONTEXT_ENVIRONMENT_KEY, "environment", context
        .getEnvironment(), loader);
    loadConfigurationDimension(configurationRoot, ConfigurationManager.CONTEXT_VMTYPE_KEY, "vm-type", context
        .getVmType(), loader);
    loadConfigurationDimension(configurationRoot, ConfigurationManager.CONTEXT_HOST_KEY, "host", context.getHost(),
        loader);
    loadConfigurationDimension(configurationRoot, ConfigurationManager.CONTEXT_VMID_KEY, "vm-id", context.getVmId(),
        loader);

//    log.debug("== Active Configuration start == ");
//    log.debug(ConfigurationToString.toStringBuilder(configurationManager));
//    log.debug("== Active Configuration end == ");

    // resolve references
    TokenResolver resolver = new TokenResolver();
    resolver.resolve((ConfigurationBranch) configurationManager.getRoot());
    
    log.debug("== Active Configuration start == ");
    log.debug(ConfigurationToString.toStringBuilder(configurationManager));
    log.debug("== Active Configuration end == ");
    
    // load into properties for use by Spring.
    Properties allProps = new Properties();
    PropertiesLoader.loadProperties((ConfigurationBranch) configurationManager.getRoot(), allProps);
    super.setProperties(allProps);

    displayContext(context);
  }

  /**
   * Returns the <code>ConfigurationManager</code> used by this configurator.
   * @return the <code>ConfigurationManager</code> used
   */
  public ConfigurationManager getConfigurationManager() {
    return configurationManager;
  }

  /**
   * Returns the comma-separated list of files and paths to ignore.
   * @return a <code>String</code> containing the list
   */
  public String getIgnoreList() {
    return ignoreList;
  }

  /**
   * Sets the comma-separated list of files and paths to ignore.
   * @param ignoreList a comma-separated list of files and paths to ignore
   */
  public void setIgnoreList(final String ignoreList) {
    log.info("Set ignore list to: " + ignoreList);
    this.ignoreList = ignoreList;
  }

  /**
   * Returns the root of this configuration.
   * @return a <code>String</code> containing the root
   */
  public String getConfigurationRoot() {
    return configurationRoot;
  }

  /**
   * Sets the root for this configuration.
   *
   * @param configurationRoot the root directory to set
   * @throws RuntimeException if configurationRoot is null
   */
  public void setConfigurationRoot(String configurationRoot) {
    if (configurationRoot == null) {
      throw new RuntimeException(
        "Configuration root cannot be null.  It must be set using environment variable MARKETLIVE_SITE_HOME, or via the constructor");
    }
    log.info("Set configuration Root to: " + configurationRoot);
    this.configurationRoot = configurationRoot;
  }

  /**
   * Loads the specified dimension using specified root, name, path and value.
   *
   * @param configurationRoot the root of dimension to load
   * @param dimensionName the name of the dimension to load
   * @param dimensionPath the path of the dimension to load
   * @param dimensionValue the value of the dimension to load
   * @param loader the loader to use to load the dimension
   */
  private void loadConfigurationDimension(String configurationRoot, String dimensionName, String dimensionPath,
      String dimensionValue, FileSystemLoader loader) {
    if (dimensionValue == null) {
      log.info("No value specified so not loading configuration for dimension: " + dimensionName);
    }

    if (dimensionValue != null) {
      File configDir = new File(configurationRoot + "/" + dimensionPath + "/" + dimensionValue);

      if (configDir.exists()) {
        log.info("Loading configuration dimension from: " + configDir.getAbsolutePath());
        loader.loadFromFileSystem(configurationManager, configDir);
      } else {
        log.warn("Could not find configuration path, not loading dimension: " + configDir.getAbsolutePath());
      }
    }

  }

  /**
   * Utility method to display on console what is known about the environment.
   *
   * @param context the IConfigurationContext
   */
  private void displayContext(IConfigurationContext context) {
    System.out.println("**********************************************************************");
    System.out.println("*                   System Configuration Values                      *");
    System.out.println("* ------------------------------------------------------------------ *");
    System.out.println("* MarketLive Version: " + getFullPlatformVersion());
    System.out.println("* Site: " + context.getClient());
    System.out.println("* Environment: " + context.getEnvironment());
    System.out.println("* VM-Type: " + context.getVmType());
    System.out.println("* Host: " + context.getHost());
    System.out.println("* VM-ID: " + context.getVmId());
    System.out.println("* System Locale: " + context.getLocale());
    // System.out.println("* DB: " + getAsString("system.db.url"));
    System.out.println("**********************************************************************");
  }

  // Some of the lines are longer than the filler! So we were seeing IndexOutOfBounds: -33!
  // Just don't put a right-hand edge on the display!
  // private String getFormatedLine(String line){
  // String filler = " *";
  // return line + filler.substring(line.length(), filler.length());
  // }

  /**
   * Returns the full platform version.
   *
   * @return the full platform version
   */
  private String getFullPlatformVersion() {
    IConfigurationBranch versionBranch = configurationManager.getBranch("system.version");
    IConfigurationLeaf[] versions = versionBranch.getLeaves();

    for (int i = 0; i < versions.length; i++) {
      if ("platform".indexOf(versions[i].getName().toLowerCase()) >= 0) {
        return versions[i].getAsString();
      }
    }
    return "unknown";
  }
}
